/*
 * Copyright 2008-2010 Daniel Cachapa <cachapa@gmail.com>
 * 
 * This program is distributed under the terms of the GNU General Public License Version 3
 * The license can be read in its entirety in the LICENSE.txt file accompanying this source code,
 * or at: http://www.gnu.org/copyleft/gpl.html
 * 
 * This file is part of Libra.
 *
 * WeightWatch is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, version 3 of the License.
 *
 * WeightWatch is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with the WeightWatch source code. If not, see: http://www.gnu.org/licenses
 */

package net.cachapa.libra.dailyburn;

import java.util.LinkedList;

import net.cachapa.libra.data.Value;
import net.cachapa.libra.util.UnitManager;
import oauth.signpost.OAuth;
import oauth.signpost.OAuthConsumer;
import oauth.signpost.OAuthProvider;
import oauth.signpost.basic.DefaultOAuthProvider;
import oauth.signpost.commonshttp.CommonsHttpOAuthConsumer;
import oauth.signpost.exception.OAuthNotAuthorizedException;
import oauth.signpost.signature.SignatureMethod;

import org.apache.http.HttpResponse;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.HTTP;

import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;

public class DailyBurnApi {
	private static final String CONSUMER_KEY = "NHugw9P7jYp2t4fc5voIg";
	private static final String CONSUMER_SECRET = "kLTzIedrzHhy8723uDfcPVeXioX3RrV4bOlaFGWJ74";
	private static final String REQUEST_TOKEN_URL = "http://dailyburn.com/api/oauth/request_token";
	private static final String ACCESS_TOKEN_URL = "http://dailyburn.com/api/oauth/access_token";
	private static final String AUTHORIZE_URL = "http://dailyburn.com/api/oauth/authorize";
	private static final String REQUEST_TOKEN = "request_token";
	private static final String REQUEST_SECRET = "request_secret";
	private static final String LOGGED_IN = "logged_in";
	private static final String PREFERENCES_FILE = "oauth";
	public static final String CALLBACK_URI = "libra://sync";
	
	public static final String USER_DETAILS = "https://dailyburn.com/api/users/current.xml";
	public static final String BODY_LOG_ENTRIES = "https://dailyburn.com/api/body_log_entries.xml";
	
	private Context context;
	private static DailyBurnApi instance = null;
	private OAuthConsumer consumer;
	private OAuthProvider provider;
	private SharedPreferences preferences;
	
	protected DailyBurnApi(Context context) {
		this.context = context;
		preferences = context.getSharedPreferences(PREFERENCES_FILE, Context.MODE_PRIVATE);
		consumer = loadConsumer();
		provider = new DefaultOAuthProvider(consumer, REQUEST_TOKEN_URL, ACCESS_TOKEN_URL, AUTHORIZE_URL); 
	}
	
	public static DailyBurnApi getInstance(Context context) {
		if (instance == null) {
			instance = new DailyBurnApi(context);
		}
		return instance;
	}
	
	public void login() throws Exception {
		// Get the authorization URL
		String authUrl = provider.retrieveRequestToken(CALLBACK_URI);
		saveConsumer();
		// Open the URL for the user to log in
		context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(authUrl)));
	}
	
	public void completeLogin(Uri uri) {
		String verifier = uri.getQueryParameter(OAuth.OAUTH_VERIFIER);
		try {
			provider.retrieveAccessToken(verifier);
			SharedPreferences.Editor editor = preferences.edit();
			editor.putBoolean(LOGGED_IN, true);
			editor.commit();
		} catch (Exception e) {
			e.printStackTrace();
		}
		saveConsumer();
	}
	
	public void logout() {
		consumer.setTokenWithSecret(null, null);
		SharedPreferences.Editor editor = preferences.edit();
		editor.putBoolean(LOGGED_IN, false);
		editor.commit();
		saveConsumer();
	}
	
	public boolean isLoggedIn() {
		return preferences.getBoolean(LOGGED_IN, false);
	}
	
	public String get(String url) {
		String response = null;
		try {
			// Create and sign the request
			HttpGet get = new HttpGet(url);
			consumer.sign(get);

			// Get the information from the server
			DefaultHttpClient client = generateHackedHttpClient();
			response = client.execute(get, new BasicResponseHandler());
		} catch (Exception e) {
			e.printStackTrace();
		}
		return response;
	}
	
	public void postValue(Value value) throws Exception {
		HttpPost post = new HttpPost(BODY_LOG_ENTRIES);
		
		String weightUnit = UnitManager.getInstance(context).getWeightUnit().toLowerCase();
		LinkedList<BasicNameValuePair> data = new LinkedList<BasicNameValuePair>();
		data.add(new BasicNameValuePair("body_log_entry[body_metric_identifier]", "body_weight"));
		data.add(new BasicNameValuePair("body_log_entry[value]", value.getWeight() + ""));
		data.add(new BasicNameValuePair("body_log_entry[unit]", weightUnit));
		data.add(new BasicNameValuePair("body_log_entry[logged_on]", value.getDate().toStandardString()));
		post.setEntity(new UrlEncodedFormEntity(data, HTTP.UTF_8));
		
		// set this to avoid 417 error (Expectation Failed)
		post.getParams().setBooleanParameter(CoreProtocolPNames.USE_EXPECT_CONTINUE, false);
		
		consumer.sign(post);
		DefaultHttpClient client = generateHackedHttpClient();
		
		HttpResponse response = client.execute(post);
		int statusCode = response.getStatusLine().getStatusCode();
		// Finish the connection
		response.getEntity().consumeContent();
		// Finally check if the response code is 200. If it isn't, then we generate an exception.
		if (statusCode != 200) {
			throw new OAuthNotAuthorizedException();
		}
	}
		
	private void saveConsumer() {
		SharedPreferences.Editor editor = preferences.edit();
		String token = consumer.getToken();
		String secret = consumer.getTokenSecret();
		if(token == null) {
			editor.remove(REQUEST_TOKEN);
		}
		else {
			editor.putString(REQUEST_TOKEN, token);
		}
		if (secret == null) {
			editor.remove(REQUEST_SECRET);
		}
		else {
			editor.putString(REQUEST_SECRET, secret);
		}
		editor.commit();
	}
	
	private OAuthConsumer loadConsumer() {
		OAuthConsumer c = new CommonsHttpOAuthConsumer(CONSUMER_KEY, CONSUMER_SECRET, SignatureMethod.HMAC_SHA1);
		String token = preferences.getString(REQUEST_TOKEN, null);
		String secret = preferences.getString(REQUEST_SECRET, null);
		if (token != null && secret != null) {
			c.setTokenWithSecret(token, secret);
		}
		return c;
	}
	
	/**
	 * This method creates a DefaultHttpClient which ignores SSL certificate name mismatches in the URL.
	 * I kept getting an error in how dailyburn.com doesn't match to *.dailyburn.com. I think it's a bug
	 * somewhere within the class' framework, but I'm not sure.
	 * In any case, this code stays until a better solution comes along.
	 * @return DefaultHttpClient which ignores SSL certificate domain name mismatches
	 */ 
	private DefaultHttpClient generateHackedHttpClient() {
		HttpParams parameters = new BasicHttpParams();
		SchemeRegistry schemeRegistry = new SchemeRegistry();
		SSLSocketFactory sslSocketFactory = SSLSocketFactory.getSocketFactory();
		sslSocketFactory.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
		schemeRegistry.register(new Scheme("https", sslSocketFactory, 443));
		ClientConnectionManager manager = new ThreadSafeClientConnManager(parameters, schemeRegistry);
		return new DefaultHttpClient(manager, parameters);
	}
}
